package org.lc.gateway.filter;


import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.lc.gateway.context.GatewayContext;
import org.lc.gateway.enums.FilterOrderEnum;
import org.lc.gateway.properties.GatewayPluginProperties;
import org.lc.gateway.util.IpUtils;
import org.lc.gateway.util.SchemeUtil;
import org.lc.platform.base.constant.LcConstant;
import org.lc.platform.base.domain.GatewayApiLog;
import org.lc.platform.base.util.LoggerUtil;
import org.lc.platform.redis.constant.RedisConstant;
import org.lc.platform.redis.dto.MessageDto;
import org.lc.platform.redis.service.CacheService;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Objects;

/**
 * Filter To Log Request And Response(exclude response body)
 *
 * @author dl
 */
@Slf4j
@Component
@AllArgsConstructor
public class RequestLogFilter implements GlobalFilter, Ordered {

    private GatewayPluginProperties gatewayPluginProperties;
    private CacheService cacheService;

    private GatewayApiLog getGateWayLogInfo(ServerWebExchange exchange) {
        /* 获取请求request 相关参数*/
        URI requestURI = exchange.getRequest().getURI();
        String scheme = requestURI.getScheme();
        long startTime = Convert.toLong(exchange.getAttribute(SchemeUtil.START_TIME));
        long endTime = System.currentTimeMillis();
        Long duration = (endTime - startTime);
        ServerHttpResponse response = exchange.getResponse();
        /* 映射到相关对象中 */
        GatewayApiLog gatewayApiLog = new GatewayApiLog();
        gatewayApiLog.setClientHost(requestURI.getHost());
        gatewayApiLog.setClientIp(IpUtils.getIP(exchange.getRequest()));
        gatewayApiLog.setStartTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault()));
        gatewayApiLog.setEndTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(endTime), ZoneId.systemDefault()));
        gatewayApiLog.setDuration(duration);
        gatewayApiLog.setMethod(exchange.getRequest().getMethod().name());
        gatewayApiLog.setScheme(scheme);
        gatewayApiLog.setRequestUri(requestURI.getPath());
        gatewayApiLog.setResponseCode(String.valueOf(Objects.requireNonNull(response.getStatusCode()).value()));
        return gatewayApiLog;
    }


    /**
     * log api request
     * <p>
     * {exchange}
     */
    @SuppressWarnings("all")
    private void logApiRequest(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        var header = exchange.getResponse().getHeaders();
        var logger = LoggerUtil.decoderLoggerStr(header.getFirst(LcConstant.X_LOG_ID));
        if (logger.getUserId() != null) {
            var gatewayApiLog = getGateWayLogInfo(exchange);
            gatewayApiLog.setOperatorId(logger.getUserId());
            gatewayApiLog.setCategory(logger.getCategory());
            GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
            /* 记录参数请求日志  */
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            if (!queryParams.isEmpty()) {
                gatewayApiLog.setQueryParams(JSONUtil.toJsonStr(queryParams));
            }
            HttpHeaders headers = request.getHeaders();
            MediaType contentType = headers.getContentType();
            if (headers.getContentLength() > 0 && ObjUtil.isNotNull(contentType)) {
                gatewayApiLog.setRequestBody(
                        contentType.includes(MediaType.APPLICATION_JSON)
                                ? gatewayContext.getRequestBody() :
                                (
                                        contentType.includes(MediaType.APPLICATION_FORM_URLENCODED)
                                                ? JSONUtil.toJsonStr(gatewayContext.getFormData()) : null)
                );
            }
            /* 记录参数返回日志 */
            if (logger.isEnabledResponse()) {
                try {
                    gatewayApiLog.setResponseBody(JSONUtil.toJsonStr(gatewayContext.getResponseBody()));
                } catch (Exception e) {
                    log.info("记录API日志返回数据转换JSON发生错误:{0}", e);
                }
            }
            /* TODO 资源有限 暂时先使用redis 进行消息的订阅发布，存储日志 */
            var message = new MessageDto();
            message.setTitle("日志记录");
            message.setContent(JSONUtil.toJsonStr(gatewayApiLog));
            cacheService.getRedis().convertAndSend(RedisConstant.LOGGER_CHANNEL, message);
        }
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
        /* 当不是TCP 请求 或者没有请求数据的时候直接放行  */
        var isNoRequestData = ObjUtil.isNotNull(gatewayContext) && !gatewayContext.getReadRequestData();
        if (SchemeUtil.isNoHttpAndHttps(request.getURI().getScheme()) || isNoRequestData) {
            return chain.filter(exchange);
        }
        long startTime = System.currentTimeMillis();
        exchange.getAttributes().put(SchemeUtil.START_TIME, startTime);
        /* 当返回参数为true时，记录请求参数和返回参数 */
        if (gatewayPluginProperties.getEnabledLog()) {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                this.logApiRequest(exchange);
            }));
        } else {
            return chain.filter(exchange);
        }
    }


    @Override
    public int getOrder() {
        return FilterOrderEnum.REQUEST_LOG_FILTER.getOrder();
    }

}
